package com.spukmk3me.video;

import com.spukmk3me.scene.Entity;

public class Text extends Entity
{
    public Text( Font font )
    {
        super( font );
        m_font = font;
    }

    @Override
    public byte getType()
    {
        return ET_TEXT;
    }

    @Override
    public Entity duplicate()
    {
        return null;
    }
    
    @Override
    public int getWidth()
    {
        return 0;
    }
    
    @Override
    public int getHeight()
    {
        return 0;
    }
    
    @Override
    public boolean isTimeless()
    {
        return true;
    }
    
    public void setText( String s, TextSetting setting )
    {
        m_str = s;
        m_textSetting = setting;
        m_filteredStr = m_str.toCharArray();
        preprocess();
    }
    
    public String getText()
    {
        return m_str;
    }
    
    public TextSetting getFontSetting()
    {
        return m_textSetting;
    }
    
    public Font getFont()
    {
        return m_font;
    }
    
    /**
     *  For internal use only.
     *  @return
     */
    public char[] getFilteredString()
    {
        return m_filteredStr;
    }
    
    /**
     *  For internal use only;
     *  @return
     */
    public TextLineInfo[] getLineInfo()
    {
        return m_lineInfo;
    }
    
    private void preprocess()
    {
        int nLine;
        int renderedLength = 0;
        
        if ( !m_textSetting.truncate )
        {
            nLine = 1;
            renderedLength = m_filteredStr.length;

            for ( int i = 0; i != m_filteredStr.length; ++i )
            {
                if ( m_filteredStr[ i ] == '\n' )
                    ++nLine;
            }
        }
        else
        {
            int lastIndex       = 0, nextIndex = 0;
            int remainedWidth   = m_textSetting.w, nextWidth;
            int copyBorder;
            
            // Ambiguous separator is a separator which was temporary
            // space, but it can become end of line character if the next word
            // doesn't fit the remaining width.
            boolean hasAmbiguousSeparator = false;
            boolean lastSeparatorWasNewLine = false;
            char    c = 0;

            nLine = 1;

            while ( lastIndex != m_str.length() )
            {
                while ( nextIndex != m_str.length() )
                {
                    c = m_str.charAt( nextIndex++ );

                    if ( (c == ' ') || (c == '\n') )
                        break;
                }
                
                if ( lastSeparatorWasNewLine )
                    remainedWidth = -1;
                
                lastSeparatorWasNewLine = c == '\n';
                
                copyBorder = ( nextIndex == m_str.length() )?
                    nextIndex : nextIndex - 1;
                nextWidth = m_font.getStringWidth(
                    m_filteredStr, lastIndex, copyBorder - lastIndex );
                
                if ( remainedWidth < nextWidth )
                {
                    if ( hasAmbiguousSeparator )
                        --renderedLength;

                    if ( remainedWidth < m_textSetting.w )
                    {
                        m_filteredStr[ renderedLength++ ] = '\n';
                        ++nLine;
                    }

                    renderedLength += copyBorder - lastIndex;

                    if ( m_textSetting.w < nextWidth )
                    {
                        if ( nextIndex != m_filteredStr.length )
                        {
                            m_filteredStr[ renderedLength++ ] = '\n';
                            ++nLine;
                            remainedWidth = m_textSetting.w;
                            hasAmbiguousSeparator = false;
                        }
                    }
                    else
                    {
                        remainedWidth = m_textSetting.w - nextWidth;
                        hasAmbiguousSeparator = (copyBorder != m_filteredStr.length);
                    }
                }
                else
                {
                    renderedLength += copyBorder - lastIndex;
                    remainedWidth -= nextWidth;
                    hasAmbiguousSeparator = (copyBorder != m_filteredStr.length);
                }

                if ( hasAmbiguousSeparator )
                {
                    m_filteredStr[ renderedLength++ ] = ' ';
                    remainedWidth -= m_font.getCharacterDimension( ' ' ).w;
                }

                lastIndex = nextIndex;
            }
        }

        // Calculate offset, length and text width for each line
        {
            int lineI = 0;
            TextLineInfo info;
            
            m_lineInfo = new TextLineInfo[ nLine ];
            
            for ( int i = 0; i != nLine; ++i )
                m_lineInfo[ i ] = new TextLineInfo();
    
            for ( int i = 0; i != renderedLength; ++i )
            {
                if ( (m_filteredStr[ i ] == '\n') || (i == renderedLength - 1) )
                {
                    info = m_lineInfo[ lineI ];
                    
                    if ( lineI == 0 )
                        info.m_offset = 0;
                    else
                    {
                        info.m_offset = m_lineInfo[ lineI - 1 ].m_offset +
                            m_lineInfo[ lineI - 1 ].m_length + 1;
                    }
                    
                    if ( i == renderedLength - 1 )
                        info.m_length = renderedLength - info.m_offset;
                    else
                        info.m_length = i - info.m_offset;

                    info.m_w = m_font.getStringWidth(
                        m_filteredStr, info.m_offset, info.m_length );
                    
                    ++lineI;
                }
            }
        }
        
        // Adjust x, y for each line based on alignment
        {
            if ( (m_textSetting.flags & TextSetting.ALIGN_CENTERX) != 0 )
            {
                for ( int i = 0; i != nLine; ++i )
                    m_lineInfo[ i ].m_x = (m_textSetting.w - m_lineInfo[ i ].m_w) / 2;
                
                m_textWidth = m_textSetting.w;
            }
            else if ( (m_textSetting.flags & TextSetting.ALIGN_RIGHT) != 0 )
            {
                for ( int i = 0; i != nLine; ++i )
                    m_lineInfo[ i ].m_x = m_textSetting.w - m_lineInfo[ i ].m_w;
                
                m_textWidth = m_textSetting.w;
            }
            else // Calculate the text width
            {
                m_textWidth = 0;
                
                for ( int i = 0; i != nLine; ++i )
                {
                    m_lineInfo[ i ].m_x = 0;
                    
                    if ( m_lineInfo[ i ].m_w > m_textWidth )
                        m_textWidth = m_lineInfo[ i ].m_w;
                }
            }
            
            int baseY = 0;
    
            if ( (m_textSetting.flags & TextSetting.ALIGN_CENTERY) != 0 )
                baseY = (m_textSetting.h - nLine * m_font.getLineHeight()) / 2;
            else if ( (m_textSetting.flags & TextSetting.ALIGN_TOP) != 0 )
                baseY = m_textSetting.h - nLine * m_font.getLineHeight();
            else
                baseY = 0;
            
            for ( int i = 0; i != nLine; ++i )
                m_lineInfo[ i ].m_y = baseY + m_font.getLineHeight() * (nLine - 1 - i); 
        }
    }
    
    private final Font m_font;

    private TextSetting     m_textSetting;
    private String          m_str;
    private char[]          m_filteredStr;
    private TextLineInfo[]  m_lineInfo;
    private int             m_textWidth;
    
    public final class TextLineInfo
    {
        public int x()
        {
            return m_x;
        }
        
        public int y()
        {
            return m_y;
        }
        
        public int offset()
        {
            return m_offset;
        }
        
        public int length()
        {
            return m_length;
        }
        
        public int w()
        {
            return m_w;
        }
        
        private int m_x, m_y, m_offset, m_length, m_w;
    }
}
